{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Loading and normalizing CIFAR10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import torchvision\n",
    "import torchvision.transforms as transforms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "transform = transforms.Compose([\n",
    "    # 将一个 PIL Image 或者 numpy.ndarray（H x W x C）转化为 torch.Tensor（C x H x W），\n",
    "    # 并且归一化到 [0, 1]。这一步是为了把图像转化为 PyTorch 可以处理的数据类型\n",
    "    transforms.ToTensor(),\n",
    "    # Normalize 这个函数是进行标准化处理。它接收两个参数：一个是均值，一个是标准差。\n",
    "    # 这里的 (0.5, 0.5, 0.5) 表示 RGB 三个通道的均值和标准差。\n",
    "    # 对每个通道进行如下操作： image = (image - mean) / std,\n",
    "    # 假设原来的像素值是 [0, 1]，那么这个操作之后的像素值就会变成 [-1, 1],\n",
    "    # 这个操作可以使得模型训练过程中的数值更稳定。\n",
    "    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Files already downloaded and verified\n",
      "Files already downloaded and verified\n"
     ]
    }
   ],
   "source": [
    "# CIFAR-10 是一个常用的图像分类数据集，包含 60000 张 32x32 的彩色图片，有 10 个类别。\n",
    "\n",
    "trainset = torchvision.datasets.CIFAR10(root='./data', # 指定了数据集的下载路径\n",
    "                                        train=True, # 表示加载的是训练集\n",
    "                                        download=True, # 表示如果数据集没有在指定路径下找到，那么就下载数据集。如果数据集已经存在，那么这个参数没有作用。\n",
    "                                        transform=transform # 指定了一个数据预处理的函数\n",
    "                                        )\n",
    "# 创建一个数据加载器，这个加载器可以在训练模型时批量加载数据。\n",
    "trainloader = torch.utils.data.DataLoader(trainset, # 是你要加载的数据集\n",
    "                                          batch_size=4, # 指定每个 batch 的大小，也就是每次模型训练时输入的数据量。\n",
    "                                          shuffle=True, # 指在每个 epoch 开始时，要不要把数据打乱。这在训练模型时是一种常用的方式，可以增强模型的泛化能力。\n",
    "                                          num_workers=12 # 指定使用多少个子进程来加载数据\n",
    "                                          )\n",
    "\n",
    "testset = torchvision.datasets.CIFAR10(root='./data',\n",
    "                                       train=False,\n",
    "                                       download=True,\n",
    "                                       transform=transform)\n",
    "testloader = torch.utils.data.DataLoader(testset,\n",
    "                                         batch_size=4,\n",
    "                                         shuffle=False,\n",
    "                                         num_workers=12)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 10个类别\n",
    "classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse',\n",
    "           'ship', 'truck')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "def imshow(img):\n",
    "    # 显示一个图像,这个图像是一个PyTorch的Tensor，具有3个维度，分别代表颜色通道、高度和宽度 (C, H, W)\n",
    "    img = img / 2 + 0.5  # 对图像数据进行反标准化, 标准化后的图像的像素值是在[-1, 1]之间, 反标准化就是将这些值再变回原来的[0, 1]区间。\n",
    "    npimg = img.numpy() # 将 Tensor 转化为 NumPy 数组\n",
    "    plt.imshow(np.transpose(npimg, (1, 2, 0))) # 使用了np.transpose方法对图像的维度进行了调换，Matplotlib希望的输入是（H, W, C）形式的，所以需要进行转换。\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 创建了一个迭代器，这个迭代器可以从trainloader中一次获取一个batch的数据。\n",
    "dataiter = iter(trainloader)\n",
    "# 使用next函数从dataiter中获取了一个batch的数据，这个batch包含了4张图像（因为batch_size=4）以及这些图像对应的标签。\n",
    "images, labels = next(dataiter)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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6K/+6q+gKNbp1D+PPfYYvf/976f+/n5yHIfuv/9+sM+nL6ampui6666jtrY2SqVS9IlPfIJyuVwLz+KNo1n/VKtV+tznPkfnnnsuxWIx6u/vp+uvv56OHj0K+3gr989xY96E3HPPPSYYDJp//ud/Ni+88IL5oz/6I5NKpczo6OjJblpLufLKK81dd91lduzYYbZv325+53d+xwwNDZlcLtfY5lOf+pQZHBw0mzdvNs8++6y55JJLzKWXXnoSW31yePrpp83ixYvN6tWrzWc+85nG56dz/0xNTZlFixaZj370o+app54y+/btMw8++KDZu3dvY5vbbrvNJJNJc//995vnnnvOfOADHzBLliwxxWLxJLa8Ndx6662ms7PT/OhHPzL79+839957r4nH4+brX/96Y5vTqX9+8pOfmC9+8YvmBz/4gSEic99994F9Pn3x3ve+15x33nnmySefND//+c/NGWecYa699toWn8kbQ7P+SafTZv369eZ73/ue2bVrl9myZYtZu3atWbNmDezjrdw/x8ub8uVj7dq1ZsOGDY31er1u+vv7zaZNm05iq04+Y2NjhojMo48+aoz59YAPBALm3nvvbWzz4osvGiIyW7ZsOVnNbDnZbNYsX77cPPTQQ+ad73xn4+XjdO+fz33uc+btb3/7nHbP80xfX5/5u7/7u8Zn6XTahEIh8+///u+taOJJ5X3ve5/5+Mc/Dp9dffXV5rrrrjPGnN79I39c59MXO3fuNERknnnmmcY2P/3pT43jOObIkSMta3sreKWXM8nTTz9tiMgcPHjQGHN69c98eNPJLpVKhbZu3Urr169vfOa6Lq1fv562bNlyElt28pmZmSEioo6ODiIi2rp1K1WrVeirFStW0NDQ0GnVVxs2bKD3ve990A9E2j//+Z//SRdeeCH93u/9HvX09NAFF1xA//RP/9Sw79+/n0ZGRqB/kskkXXzxxadF/1x66aW0efNmeumll4iI6LnnnqPHH3+crrrqKiLS/rGZT19s2bKFUqkUXXjhhY1t1q9fT67r0lNPPdXyNp9sZmZmyHEcSqVSRKT9I3nTVbWdmJiger1Ovb298Hlvby/t2rXrJLXq5ON5Ht1444102WWX0apVq4iIaGRkhILBYGNw/4be3l4aGRk5Ca1sPffccw/98pe/pGeeeWaW7XTvn3379tEdd9xBN998M33hC1+gZ555hv70T/+UgsEg3XDDDY0+eKV77XTon89//vOUyWRoxYoV5PP5qF6v06233krXXXcdEdFp3z828+mLkZER6unpAbvf76eOjo7Trr9KpRJ97nOfo2uvvbZR2Vb7B3nTvXwor8yGDRtox44d9Pjjj5/sprxpGB4eps985jP00EMPUTgcPtnNedPheR5deOGF9Dd/8zdERHTBBRfQjh076Fvf+hbdcMMNJ7l1J5/vf//79N3vfpfuvvtuOuecc2j79u104403Un9/v/aP8pqpVqv0+7//+2SMoTvuuONkN+dNy5tOdunq6iKfzzcrImF0dJT6+vpOUqtOLhs3bqQf/ehH9Mgjj9DAwEDj876+PqpUKpROp2H706Wvtm7dSmNjY/S2t72N/H4/+f1+evTRR+kb3/gG+f1+6u3tPa37Z8GCBXT22WfDZytXrqRDhw4RETX64HS91/7sz/6MPv/5z9NHPvIROvfcc+kP//AP6aabbqJNmzYRkfaPzXz6oq+vj8bGxsBeq9VoamrqtOmv37x4HDx4kB566KHGrAeR9o/kTffyEQwGac2aNbR58+bGZ57n0ebNm2ndunUnsWWtxxhDGzdupPvuu48efvhhWrJkCdjXrFlDgUAA+mr37t106NCh06Kv3v3ud9Pzzz9P27dvb/xdeOGFdN111zWWT+f+ueyyy2aFZr/00ku0aNEiIiJasmQJ9fX1Qf9kMhl66qmnTov+KRQK5Lr4CPT5fOR5HhFp/9jMpy/WrVtH6XSatm7d2tjm4YcfJs/z6OKLL255m1vNb1489uzZQ//93/9NnZ2dYD/d+2cWJ9vj9ZW45557TCgUMt/+9rfNzp07zSc/+UmTSqXMyMjIyW5aS/njP/5jk0wmzc9+9jNz7Nixxl+hUGhs86lPfcoMDQ2Zhx9+2Dz77LNm3bp1Zt26dSex1ScXO9rFmNO7f55++mnj9/vNrbfeavbs2WO++93vmmg0av7t3/6tsc1tt91mUqmU+eEPf2h+9atfmQ9+8INv2VBSyQ033GAWLlzYCLX9wQ9+YLq6usxnP/vZxjanU/9ks1mzbds2s23bNkNE5u///u/Ntm3bGtEa8+mL9773veaCCy4wTz31lHn88cfN8uXL3zKhpM36p1KpmA984ANmYGDAbN++HZ7X5XK5sY+3cv8cL2/Klw9jjPmHf/gHMzQ0ZILBoFm7dq158sknT3aTWg4RveLfXXfd1dimWCyaP/mTPzHt7e0mGo2a3/3d3zXHjh07eY0+yciXj9O9f/7rv/7LrFq1yoRCIbNixQrzj//4j2D3PM986UtfMr29vSYUCpl3v/vdZvfu3Septa0lk8mYz3zmM2ZoaMiEw2GzdOlS88UvfhF+LE6n/nnkkUde8Xlzww03GGPm1xeTk5Pm2muvNfF43LS1tZmPfexjJpvNnoSzOfE065/9+/fP+bx+5JFHGvt4K/fP8eIYY6XzUxRFURRFeYN50/l8KIqiKIry1kZfPhRFURRFaSn68qEoiqIoSkvRlw9FURRFUVqKvnwoiqIoitJS9OVDURRFUZSWoi8fiqIoiqK0FH35UBRFURSlpejLh6IoiqIoLUVfPhRFURRFaSn68qEoiqIoSkv5/wFISdpHYvbESgAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "horse  deer plane horse\n"
     ]
    }
   ],
   "source": [
    "# 使用了torchvision.utils.make_grid函数，这个函数将多张图像拼接在一起，\n",
    "# 形成一个网格状的大图像。然后用 imshow 函数将这个大图像显示出来。\n",
    "imshow(torchvision.utils.make_grid(images))\n",
    "# 打印出每张图像对应的标签\n",
    "print(' '.join('%5s' % classes[labels[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Define a Convolutional Neural Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "\n",
    "class Net(nn.Module):\n",
    "    '''特征图尺寸的计算公式为：[(原图片尺寸 — 卷积核尺寸) / 步长 ] + 1'''\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        # 第一个卷积层，输入通道数是3（因为CIFAR-10图像是彩色的，有红绿蓝三个通道），输出通道数是6，卷积核的大小是5x5。\n",
    "        # 输入是32*32*3，计算（32-5）/ 1 + 1 = 28，那么通过conv1输出的结果是28*28*6\n",
    "        self.conv1 = nn.Conv2d(3, 6, 5)\n",
    "        # 最大池化层，窗口大小和步长都是2\n",
    "        # 输入是28*28*6，窗口2*2，计算28 / 2 = 14，那么通过max_pool1层输出结果是14*14*6\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        # 第二个卷积层，输入通道数是6（来自上一层的输出），输出通道数是16，卷积核的大小是5x5。\n",
    "        # 输入是14*14*6，计算（14 - 5）/ 1 + 1 = 10，那么通过conv2输出的结果是10*10*16\n",
    "        self.conv2 = nn.Conv2d(6, 16, 5)\n",
    "        # 第一个全连接层，输入节点数是 16 * 5 * 5，输出节点数是 120。\n",
    "        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n",
    "        # 第二个全连接层，输入节点数是 120，输出节点数是 84。\n",
    "        self.fc2 = nn.Linear(120, 84)\n",
    "        # 第三个全连接层，输入节点数是84，输出节点数是10。输出节点数为10是因为CIFAR-10数据集有10个类别。\n",
    "        self.fc3 = nn.Linear(84, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        '''定义了网络的前向传播函数。在PyTorch中，只需要定义前向传播函数，\n",
    "        后向传播函数会通过自动求导机制自动生成。这个函数的输入是一个batch的图像数据，\n",
    "        输出是这个batch在每个类别上的得分。'''\n",
    "        # 第一层卷积，然后通过激活函数ReLU，然后进行最大池化。\n",
    "        # 32x32x3 --> 28x28x6 --> 14x14x6\n",
    "        x = self.pool(F.relu(self.conv1(x)))\n",
    "        # 第二层卷积，然后通过激活函数ReLU，然后进行最大池化。\n",
    "        # 14x14x6 --> 10x10x16 --> 5x5x16\n",
    "        x = self.pool(F.relu(self.conv2(x)))\n",
    "        # 将二维的特征图(featue map)展平为一维，准备输入到全连接层。\n",
    "        x = x.view(-1, 16 * 5 * 5)\n",
    "        # 第一个全连接层，然后通过激活函数ReLU\n",
    "        x = F.relu(self.fc1(x))\n",
    "        # 第二个全连接层，然后通过激活函数ReLU\n",
    "        x = F.relu(self.fc2(x))\n",
    "        # 第三个全连接层，输出层\n",
    "        x = self.fc3(x)\n",
    "        # x是网络的输出，是一个大小为（batch_size, 10）的Tensor，每一行代表一个图像在每个类别上的得分。\n",
    "        return x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=400, out_features=120, bias=True)\n",
       "  (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
       "  (fc3): Linear(in_features=84, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "net = Net()\n",
    "\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "net.to(device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Define a Loss function and optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch.optim as optim\n",
    "\n",
    "# 定义了损失函数，这里使用的是交叉熵损失。对于分类问题，交叉熵损失是一种常用的损失函数。\n",
    "# 这个函数将模型的输出（即在每个类别上的得分）和真实的标签作为输入，计算出一个标量值，代表了模型的损失。\n",
    "# 模型的训练目标就是最小化这个损失。\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "\n",
    "# 在每一步训练中，优化器都会根据损失函数的梯度来更新模型的参数，从而减小损失。\n",
    "optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Train the network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1,  2000] loss: 2.201\n",
      "[1,  4000] loss: 1.893\n",
      "[1,  6000] loss: 1.709\n",
      "[1,  8000] loss: 1.570\n",
      "[1, 10000] loss: 1.533\n",
      "[1, 12000] loss: 1.464\n",
      "[2,  2000] loss: 1.383\n",
      "[2,  4000] loss: 1.356\n",
      "[2,  6000] loss: 1.342\n",
      "[2,  8000] loss: 1.287\n",
      "[2, 10000] loss: 1.286\n",
      "[2, 12000] loss: 1.260\n",
      "Finished Training\n"
     ]
    }
   ],
   "source": [
    "for epoch in range(2):  # loop over the dataset multiple times\n",
    "    running_loss = 0.0\n",
    "    for i, data in enumerate(trainloader, 0):\n",
    "        # 对训练数据集进行遍历，每一次循环，都会从trainloader中取出一个batch的数据。\n",
    "        # get the inputs; data is a list of [inputs, labels]\n",
    "        #inputs, labels = data\n",
    "        # 获取一批训练数据和对应的标签，并将它们移到之前定义的设备（GPU或CPU）上。\n",
    "        inputs, labels = data[0].to(device), data[1].to(device)\n",
    "\n",
    "        # zero the parameter gradients\n",
    "        # 在进行反向传播之前，需要将模型的所有参数的梯度清零。\n",
    "        # 因为PyTorch的特性是累积梯度，如果不清零，梯度会被累积起来而不是被替换。\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        # forward + backward + optimize\n",
    "        # 前向传播，将输入数据 inputs 传入模型，得到输出 outputs\n",
    "        outputs = net(inputs)\n",
    "        # 将模型的输出outputs和真实的标签labels作为输入，计算得到损失loss。\n",
    "        loss = criterion(outputs, labels)\n",
    "        # 反向传播，计算损失函数关于模型参数的梯度。\n",
    "        loss.backward()\n",
    "        # 优化器根据反向传播计算得到的梯度来更新模型的参数\n",
    "        optimizer.step()\n",
    "\n",
    "        # print statistics\n",
    "        running_loss += loss.item()\n",
    "        # 每2000个mini-batches打印一次平均损失\n",
    "        if i % 2000 == 1999:  # print every 2000 mini-batches\n",
    "            print('[%d, %5d] loss: %.3f' %\n",
    "                  (epoch + 1, i + 1, running_loss / 2000))\n",
    "            # 重置累积损失\n",
    "            running_loss = 0.0\n",
    "\n",
    "print('Finished Training')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 保存模型\n",
    "\n",
    "PATH = './cifar_net.pth'\n",
    "torch.save(net.state_dict(), PATH)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Test the network on the test data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GroundTruth:    cat  ship  ship plane\n"
     ]
    }
   ],
   "source": [
    "dataiter = iter(testloader)\n",
    "images, labels = next(dataiter)\n",
    "\n",
    "# print images\n",
    "imshow(torchvision.utils.make_grid(images))\n",
    "print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<All keys matched successfully>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 从本地加载模型\n",
    "\n",
    "net = Net()\n",
    "net.load_state_dict(torch.load(PATH))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 在测试集samples上推理\n",
    "\n",
    "outputs = net(images)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.2415, -1.5627,  0.6724,  1.9120,  0.2987,  1.2558,  0.3695, -0.9628,\n",
      "         -0.6999, -1.7481],\n",
      "        [ 3.5968,  5.9252, -1.7102, -2.1320, -3.3263, -3.1412, -3.8435, -3.3993,\n",
      "          3.9825,  4.4314],\n",
      "        [ 1.5974,  2.3273, -0.7391, -0.7184, -1.2841, -1.7442, -2.2228, -1.7487,\n",
      "          2.3355,  1.6439],\n",
      "        [ 3.3059, -0.4717,  2.2933, -0.4196,  0.5006, -1.8679, -1.2899, -1.7452,\n",
      "          1.3360, -0.4505]], grad_fn=<AddmmBackward0>)\n"
     ]
    }
   ],
   "source": [
    "print(outputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicted:    cat   car  ship plane\n"
     ]
    }
   ],
   "source": [
    "# torch.max()函数找到模型输出中每个样本得分最高的类别的索引。\n",
    "# torch.max()函数会返回两个Tensor，第一个Tensor是每行的最大值，第二个Tensor是最大值所在的索引（即预测的类别）。\n",
    "\n",
    "_, predicted = torch.max(outputs, 1)\n",
    "\n",
    "print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy of the network on the 10000 test images: 56 %\n"
     ]
    }
   ],
   "source": [
    "# 评估模型在整个测试集上的性能，它计算了模型的预测正确率（accuracy），即预测正确的样本数量占总样本数量的比例。\n",
    "\n",
    "correct = 0 # 正确预测的数量\n",
    "total = 0 # 总的样本数量\n",
    "# 在评估模型的性能时，不需要更新模型的参数，也就不需要梯度。\n",
    "with torch.no_grad():\n",
    "    # 对测试数据集进行遍历\n",
    "    for data in testloader:\n",
    "        # 获取一批测试数据和对应的标签\n",
    "        images, labels = data\n",
    "        outputs = net(images)\n",
    "        # 找到模型输出中每个样本得分最高的类别的索引，即模型的预测结果。\n",
    "        _, predicted = torch.max(outputs.data, 1)\n",
    "        # 更新总的样本数量，labels.size(0)返回这个batch的样本数量。\n",
    "        total += labels.size(0)\n",
    "        # 更新正确预测的数量\n",
    "        # 首先(predicted==labels)返回一个布尔型的Tensor，表示预测结果和真实标签是否相等。\n",
    "        # 然后，使用sum()函数计算这个batch中预测正确的样本数量，最后使用item()函数将这个数量转换为Python的标量。\n",
    "        correct += (predicted == labels).sum().item()\n",
    "\n",
    "print('Accuracy of the network on the 10000 test images: %d %%' %\n",
    "      (100 * correct / total))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy of plane : 67 %\n",
      "Accuracy of   car : 68 %\n",
      "Accuracy of  bird : 42 %\n",
      "Accuracy of   cat : 24 %\n",
      "Accuracy of  deer : 50 %\n",
      "Accuracy of   dog : 50 %\n",
      "Accuracy of  frog : 77 %\n",
      "Accuracy of horse : 60 %\n",
      "Accuracy of  ship : 55 %\n",
      "Accuracy of truck : 71 %\n"
     ]
    }
   ],
   "source": [
    "# 评估模型在每个类别上的预测性能，它分别计算了模型在每个类别上的预测正确的样本数量和总的样本数量。\n",
    "\n",
    "class_correct = list(0. for i in range(10)) # 初始化每个类别的正确预测数量\n",
    "class_total = list(0. for i in range(10)) # 总的样本数量\n",
    "with torch.no_grad():\n",
    "    # 每一次循环，都会取出一个batch的数据。\n",
    "    for data in testloader:\n",
    "        # 获取一批测试数据和对应的标签\n",
    "        images, labels = data\n",
    "        outputs = net(images)\n",
    "        # 找到模型输出中每个样本得分最高的类别的索引，即模型的预测结果。\n",
    "        _, predicted = torch.max(outputs, 1)\n",
    "        # (predicted==labels)返回一个布尔型的Tensor，表示预测结果和真实标签是否相等。\n",
    "        # 然后，使用squeeze()函数移除这个Tensor中长度为1的维度，使其变成一个一维Tensor。\n",
    "        c = (predicted == labels).squeeze()\n",
    "        # 对一个batch中的四个样本进行处理\n",
    "        for i in range(4):\n",
    "            # 获取第i个样本的真实标签\n",
    "            label = labels[i]\n",
    "            # 如果模型对第i个样本的预测结果是正确的，那么c[i]的值为1，否则为0。\n",
    "            # 这里将c[i]的值累加到对应类别的正确预测数量中。\n",
    "            class_correct[label] += c[i].item()\n",
    "            # 更新对应类别的总的样本数量\n",
    "            class_total[label] += 1\n",
    "\n",
    "for i in range(10):\n",
    "    print('Accuracy of %5s : %2d %%' %\n",
    "          (classes[i], 100 * class_correct[i] / class_total[i]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. model inference with FastAPI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel\n",
    "from torchvision.transforms import transforms\n",
    "import torch\n",
    "import base64\n",
    "import io\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "app = FastAPI()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=400, out_features=120, bias=True)\n",
       "  (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
       "  (fc3): Linear(in_features=84, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载本地的模型\n",
    "\n",
    "model = Net()\n",
    "\n",
    "model.load_state_dict(torch.load(\"cifar_net.pth\"))\n",
    "\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "model.to(device)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=400, out_features=120, bias=True)\n",
       "  (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
       "  (fc3): Linear(in_features=84, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 定义输入的数据模型\n",
    "\n",
    "class Item(BaseModel):\n",
    "    img_base64: str"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "@app.post(\"/predict\")\n",
    "def predict(item: Item):\n",
    "    # 对输入的base64图片进行解码和处理\n",
    "    # 将base64编码的字符串解码回原始的二进制图像数据\n",
    "    img = base64.b64decode(item.img_base64)\n",
    "    # 将二进制图像数据转换为一个PIL的Image对象\n",
    "    img = Image.open(io.BytesIO(img))\n",
    "    # 定义transform\n",
    "    transform = transforms.Compose([\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Resize((32,32)),\n",
    "        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n",
    "    ])\n",
    "    # 图片预处理\n",
    "    img = transform(img).unsqueeze(0).to(device)\n",
    "    \n",
    "    # 进行推理\n",
    "    output = model(img)\n",
    "    \n",
    "    # 预测结果\n",
    "    # pred = output.argmax(dim=1, keepdim=True) \n",
    "    pred = torch.argmax(output, dim=1, keepdim=True)\n",
    "    \n",
    "    class_name = classes[pred]\n",
    "    \n",
    "    return {\"prediction\" : int(pred), \"class_name\": class_name}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. test by requests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import requests\n",
    "import base64\n",
    "import json\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 打开图像\n",
    "\n",
    "with open(\"test_images/img01.jpg\", \"rb\") as f:\n",
    "    img_bytes = f.read()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 转换为 base64 编码\n",
    "\n",
    "img_b64 = base64.b64encode(img_bytes).decode(\"utf-8\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 方式-1 ： 启动服务后，发送 POST 请求\n",
    "\n",
    "# response = requests.post(\"http://localhost:8000/predict\", json= {\"img_base64\": img_b64})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'prediction': 9}\n"
     ]
    }
   ],
   "source": [
    "# 方式-2 ： 直接通过函数传参实现\n",
    "\n",
    "item = Item(img_base64 = img_b64)\n",
    "\n",
    "\n",
    "print(predict(item))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
